package com.pine.app.module.security.oauth.provider;

import com.pine.app.module.security.core.userdetails.UserDetails;
import com.pine.app.module.security.core.common.AuthConstant;
import com.pine.app.module.security.core.common.enums.ErrorType;
import com.pine.app.module.security.oauth.exception.AuthenticationException;
import com.pine.app.module.security.core.common.SpringContextUtils;
import com.pine.app.module.security.oauth.provider.client.ClientDetails;
import com.pine.app.module.security.oauth.provider.client.ClientDetailsService;
import com.pine.app.module.security.oauth.provider.token.OAuth2AccessToken;
import com.pine.app.module.security.oauth.support.OAuthRequestValidator;
import com.pine.app.module.security.oauth.support.HttpTokenRequest;
import com.pine.app.module.security.oauth.support.refresh.HttpRefreshTokenRequest;
import com.pine.app.module.security.oauth.provider.token.AccessTokenService;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author xiaoyuan
 * @create 2020/3/7 14:39
 **/
@Slf4j
public class DefaultOauth2TokenBuilder implements OAuth2TokenBuilder {

    private static Map<String, OAuthRequestValidator> auth2AuthenticatorMap;
    private HttpTokenRequest tokenRequest;
    private OAuthRequestValidator oAuth2Authenticator;

    private static AccessTokenService accessTokenService;

    private static ClientDetailsService clientDetailsService;

    private OAuth2Authentication oAuth2Authentication ;

    public DefaultOauth2TokenBuilder() {
        if (auth2AuthenticatorMap == null || auth2AuthenticatorMap.size() == 0) {
            Collection<OAuthRequestValidator> authenticators = Collections.emptyList();
            //获取oauth的请求的校验接口所有实现类
            Map<String, OAuthRequestValidator> integrationAuthenticatorMap = SpringContextUtils.getBeanMap(OAuthRequestValidator.class);
            if (integrationAuthenticatorMap != null) {
                authenticators = integrationAuthenticatorMap.values();
            }
            auth2AuthenticatorMap = authenticators.stream().collect(Collectors.toMap(v -> v.getGrantType(), v -> v));
        }
        if(accessTokenService==null){
            accessTokenService = SpringContextUtils.getBean(AccessTokenService.class);
        }
        if(clientDetailsService==null){
            clientDetailsService = SpringContextUtils.getBean(ClientDetailsService.class);
        }
    }

    @Override
    public DefaultOauth2TokenBuilder createRequest(HttpServletRequest request) {
        String grantType = request.getParameter(AuthConstant.grant_type);
        oAuth2Authenticator = auth2AuthenticatorMap.get(grantType);
        if (oAuth2Authenticator == null) {
            ErrorType.ILLEGAL_GRANT_TYPE.throwThis(AuthenticationException::new);
        }
        //预处理 获取相应的TokenRequest类
        tokenRequest = oAuth2Authenticator.createRequest(request);
        return this;
    }

    @Override
    public DefaultOauth2TokenBuilder validateParams() {
        this.oAuth2Authentication =  oAuth2Authenticator.validateParams(tokenRequest);
        return this;
    }

    @Override
    public DefaultOauth2TokenBuilder validateParams(HttpTokenRequest tokenRequest) {
        oAuth2Authenticator = auth2AuthenticatorMap.get(tokenRequest.getGrantType());
        if (oAuth2Authenticator == null) {
            ErrorType.ILLEGAL_GRANT_TYPE.throwThis(AuthenticationException::new);
        }
        this.oAuth2Authentication =  oAuth2Authenticator.validateParams(tokenRequest);
        return this;
    }


    public OAuth2ClientRequest coverOauth2Request(ClientDetails clientDetails, HttpTokenRequest request){

        return  new DefaultOAuth2ClientRequest(clientDetails.getClientId(),clientDetails.getClientSecret()
                ,clientDetails.getClientName(),clientDetails.getScope(),clientDetails.getResources(),
                clientDetails.getRoles(),request.getGrantType(),clientDetails.getAccessTokenValiditySeconds()
        ,clientDetails.getRefreshTokenValiditySeconds());
    }

    public OAuth2AccessToken getAccessToken(OAuth2Authentication oAuth2Authentication){
        if(tokenRequest instanceof HttpRefreshTokenRequest){
            return getRefreshAccessToken((HttpRefreshTokenRequest) tokenRequest);
        }
        OAuth2AccessToken accessToken =  accessTokenService.getAccessToken(oAuth2Authentication);
        if(accessToken!=null){
            if(accessToken.isExpire()){
                accessTokenService.removeToken(accessToken.getAccessToken());
                accessToken = null;
            }
        }
        if(accessToken==null){
            accessToken = accessTokenService.createToken(oAuth2Authentication);
        }
        return accessToken;
    }

    public OAuth2AccessToken getRefreshAccessToken(HttpRefreshTokenRequest tokenRequest){
        OAuth2AccessToken accessToken =  accessTokenService.refreshAccessToken(tokenRequest.getRefreshToken());
        return accessToken;
    }
    @Override
    public OAuth2AccessToken createToken() {
        return getAccessToken(this.oAuth2Authentication);
    }

    @Override
    public OAuth2AccessToken createToken(HttpTokenRequest tokenRequest, UserDetails userDetails) {
        oAuth2Authentication = new OAuth2Authentication(
                coverOauth2Request(clientDetailsService.loadClientDetail(tokenRequest.getClientCredentials().getPrincipal()),tokenRequest),userDetails);
        return getAccessToken(oAuth2Authentication);
    }

    @Override
    public OAuth2Authentication createOAuth2Authentication(HttpTokenRequest tokenRequest, UserDetails userDetails) {
        oAuth2Authentication = new OAuth2Authentication(
                coverOauth2Request(clientDetailsService.loadClientDetail(tokenRequest.getClientCredentials().getPrincipal()),tokenRequest),userDetails);
        return oAuth2Authentication;
    }

    public HttpTokenRequest getTokenRequest() {
        return tokenRequest;
    }

    public OAuthRequestValidator getoAuth2Authenticator() {
        return oAuth2Authenticator;
    }

    @Override
    public OAuth2Authentication getOAuth2Authentication() {
        return oAuth2Authentication;
    }


}
